# -*- coding: utf-8 -*-
'''
Created on 09/giu/2009

@author: sax
'''
from concurrency.models import BaseConcurrentModel, RecordModifiedError, VersionField
from dateutil.parser import parse
from django.core.management.color import no_style
from django.db import IntegrityError, transaction, models, connection
from django.test import TransactionTestCase
import dateutil
import logging
import time
import unittest


#from django.test import TestCase

class ConcurrencyError( Exception ):
    pass

class ConcurrentModel( BaseConcurrentModel ):
    _version = VersionField( db_column = 'cm_version_id' )

    def __getVersion( self ):
        return self._version

    version = property( __getVersion )

class TestModel0( ConcurrentModel ):
    name0 = models.CharField( max_length = 30, blank = True, null = True )
    name = models.CharField( max_length = 30, blank = True, null = True )
    char_field = models.CharField( max_length = 30, blank = True, null = True )
    date_field = models.DateField( blank = True, null = True )

class TestModel1( TestModel0 ):
    name1 = models.CharField( max_length = 30, blank = True, null = True )

class TestModel2( TestModel1 ):
    name2 = models.CharField( max_length = 30, blank = True, null = True )

class TestModel3( TestModel2 ):
    """ necessario per testare il comportamento diverso in caso di 'order_with_respect_to' """
    name3 = models.CharField( max_length = 30, blank = True, null = True )
    fk = models.ForeignKey( TestModel2, related_name = "parent", blank = True, null = True )

    class Meta:
        order_with_respect_to = 'fk'
        ordering = 'date_field'

class TestModel0_Proxy( TestModel0 ):

    class Meta:
        proxy = True

class TestModel2_Proxy( TestModel2 ):
    class Meta:
        proxy = True

TABLES = [TestModel0, TestModel1, TestModel2, TestModel3]

class ConcurrencyTest0( unittest.TestCase ):

    def setUp( self ):
        super( ConcurrencyTest0, self ).setUp()

        transaction.enter_transaction_management()
        transaction.managed( True )
        connection.close()

        tables = connection.introspection.table_names()
        for table in TABLES:
            if not connection.introspection.table_name_converter( table._meta.db_table ) in tables:
                cursor = connection.cursor()
                sql, references = connection.creation.sql_create_model( table, no_style(), set() )
                for statement in sql:
                    cursor.execute( statement )

        #transaction.commit()

        self.TARGET = TestModel0( char_field = "New", name = "1" )


    def tearDown( self ):
        super( ConcurrencyTest0, self ).tearDown()

        tables = connection.introspection.table_names()
        for table in TABLES[::-1]:
            if connection.introspection.table_name_converter( table._meta.db_table ) in tables:
                cursor = connection.cursor()
                sql = connection.creation.sql_destroy_model( table, [] , no_style() )
                for statement in sql:
                    cursor.execute( statement )


#        transaction.commit()
        transaction.rollback()
        transaction.leave_transaction_management()

    def test_standard_insert( self ):
        #logging.debug( "Created Object_1")
        a = self.TARGET
        #logging.debug( "Now Object_1.version is %s " % a.version )
        v = a.version
        assert a.version == 0, "version is not null %s" % a.version
        a.save()
        #logging.debug( "Object_1 saved...now version is %s " % a.version )
        self.assertTrue( a.pk > 0 )
        assert a.version > v, "same or lower version after insert (%s,%s)" % ( a.version, v )
        b = self.TARGET.__class__.objects.get( pk = a.pk )
        self.assertEqual( a.version, b.version )

    def test_concurrency( self ):
        a = self.TARGET
        a.save()
        id = a.pk

        a = self.TARGET.__class__.objects.get( pk = id )
        v = a.version
        #logging.debug( "reloaded...now version is %s " % a.version )

        b = self.TARGET.__class__.objects.get( pk = id )
        assert a.version == b.version, "got same row with different version"
        time.sleep( 1 )
        a.name = "pippo"
        a.save()
        #logging.debug( "updated...now version is %s " % a.version )
        assert a.version > v, "same or lower version after update (%s,%s)" % ( a.version, v )
        self.assertRaises( RecordModifiedError, b.save )

    def test_concurrency_no_values( self ):
        t = self.TARGET.__class__()
        assert t.version == 0, "version is not null %s" % a.version
        t.save()
        self.assertTrue( t.pk > 0 )
        self.assertTrue( t.version > 0 )

    def test_force_update( self ):
        from django.db import connection, transaction, DatabaseError, IntegrityError
        t = self.TARGET.__class__()
        t.save()
        #self.assertRaises(ValueError,t.save,force_update=True )   

    def test_force_insert( self ):
        t = self.TARGET.__class__()
        assert t.version == 0, "version is not null %s" % a.version
        t.save( force_insert = True )
        self.assertTrue( t.pk > 0 )
        self.assertTrue( t.version > 0 )

    def test_concurrency_manager_list( self ):
        # test di concorrenza con list(queryset)
        for i in xrange( 3 ):
           #time.sleep(0.5)
           self.TARGET.__class__.objects.get_or_create( name = "model %s" % i )
          # transaction.commit()

        prima_serie = list( self.TARGET.__class__.objects.all() )
        seconda_serie = list( self.TARGET.__class__.objects.all() )

        for el in prima_serie:
            el.name = "model %s 2^" % el.pk
            el.save()

        for el in seconda_serie:
            el.name = "model %s 3^" % el.pk
            self.assertRaises( RecordModifiedError, el.save )

    def test_concurrency_manager_get_item( self ):
        """ test di concorrenza con list(queryset) """
        for i in xrange( 3 ):
           #time.sleep(0.5)
           self.TARGET.__class__.objects.get_or_create( name = "model %s" % i )

        prima_serie = self.TARGET.__class__.objects.all()
        seconda_serie = self.TARGET.__class__.objects.all()

        a = prima_serie[1]
        b = seconda_serie[1]
        a.name = "model %s 2^" % a.pk
        a.save()

        b.name = "model %s 3^" % b.pk
        self.assertRaises( RecordModifiedError, b.save )



class ConcurrencyTest1( ConcurrencyTest0 ):
    def setUp( self ):
        super( ConcurrencyTest1, self ).setUp()
        self.TARGET = TestModel1( char_field = "New", name = "1" )

    def test_force_update( self ):
        from django.db import connection, transaction, DatabaseError, IntegrityError
        t = self.TARGET.__class__()
        self.assertRaises( DatabaseError, t.save, force_update = True )

class ConcurrencyTest2( ConcurrencyTest0 ):
    def setUp( self ):
        super( ConcurrencyTest2, self ).setUp()
        self.TARGET = TestModel2( char_field = "New", name = "1" )

    def test_force_update( self ):
        from django.db import connection, transaction, DatabaseError, IntegrityError
        t = self.TARGET.__class__()
        self.assertRaises( DatabaseError, t.save, force_update = True )

class ConcurrencyTest3( ConcurrencyTest0 ):
    def setUp( self ):
        super( ConcurrencyTest3, self ).setUp()
        p, isnew = TestModel2.objects.get_or_create( pk = 1001, char_field = "New", name = "1" )
        #p = TestModel2.get_or_create(pk=1001, char_field="New", name="1")
        self.TARGET = TestModel3( char_field = "New", name = "1", fk = p )

    def test_force_update( self ):
        from django.db import connection, transaction, DatabaseError, IntegrityError
        t = self.TARGET.__class__()
        self.assertRaises( DatabaseError, t.save, force_update = True )

    def test_concurrency( self ):
        a = self.TARGET
        a.save()
        id = a.pk

        a = self.TARGET.__class__.objects.get( pk = id )
        v = a.version
        #logging.debug( "reloaded...now version is %s " % a.version )

        b = self.TARGET.__class__.objects.get( pk = id )
        assert a.version == b.version, "got same row with different version"
        time.sleep( 1 )
        a.name = "pippo"
        a.save()
        #logging.debug( "updated...now version is %s " % a.version )
        assert a.version > v, "same or lower version after update (%s,%s)" % ( a.version, v )
        self.assertRaises( RecordModifiedError, b.save )

class ConcurrencyTest4( ConcurrencyTest0 ):
    def setUp( self ):
        super( ConcurrencyTest4, self ).setUp()
        self.TARGET = TestModel2( char_field = "New", name = "1" )

    def test_manager( self ):
        from django.db import connection, transaction, DatabaseError, IntegrityError
        p, isnew = TestModel2.objects.get_or_create( pk = 1001, char_field = "New", name = "1" )

        assert isnew == True
        p, isnew = TestModel3.objects.get_or_create( pk = 1002, char_field = "New", name = "1", fk = p )
        assert isnew == True

#    def test_manager_2( self ):
#        from django.db import connection, transaction, DatabaseError, IntegrityError
#        p, isnew = TestModel2.objects.get_or_create( pk = 1001, char_field = "New", name = "1" )
#
#        assert isnew == True
#        p, isnew = TestModel3.objects.get_or_create( pk = 1001, char_field = "New", name = "1", fk = p )
#        assert isnew == True

class ConcurrencyTest0_Proxy( ConcurrencyTest0 ):
    def setUp( self ):
        super( ConcurrencyTest0_Proxy, self ).setUp()
        self.TARGET = TestModel0_Proxy( char_field = "New", name = "1" )

    def test_force_update( self ):
        from django.db import connection, transaction, DatabaseError, IntegrityError
        t = self.TARGET.__class__()
        t.save( force_update = True )
        t1 = TestModel0.objects.get( pk = t.pk )
        self.assertEqual( t.pk, t1.pk )
        #self.assertRaises(ValueError,t.save,force_update=True )   

class ConcurrencyTest2_Proxy( ConcurrencyTest2 ):
    def setUp( self ):
        super( ConcurrencyTest2_Proxy, self ).setUp()
        self.TARGET = TestModel2_Proxy( char_field = "New", name = "1" )
